#include <algorithm>

#include "SDL.h"

#include "asserts.hpp"
#include "dialog.hpp"
#include "draw_scenario.hpp"
#include "foreach.hpp"
#include "formatter.hpp"
#include "formula_game_logic.hpp"
#include "frame_rate_handler.hpp"
#include "human_player.hpp"
#include "label.hpp"
#include "raster.hpp"
#include "scenario.hpp"
#include "scenario_result.hpp"
#include "string_utils.hpp"
#include "wm.hpp"
#include "wml_node.hpp"

namespace game {

using namespace game_logic;

namespace {

class formula_game_logic_symbol_table : public function_symbol_table
{
public:
	expression_ptr create_function(const std::string& fn,
	                               const std::vector<expression_ptr>& args) const;
};

namespace {
std::vector<std::string> word_wrap_text(std::string text)
{
	const int CharsInLine = 100;
	std::vector<std::string> res;
	
	while(!text.empty()) {
		std::string::iterator end;
		if(text.size() <= CharsInLine) {
			end = text.end();
		} else {
			end = std::find(text.begin() + CharsInLine, text.end(), ' ');
		}

		std::string line(text.begin(), end);
		util::strip(line);
		res.push_back(line);
		text.erase(text.begin(), end);
	}

	return res;
}

}

class speech_command : public game_command_callable {
	unit_ptr speaker_;
	std::string text_;
public:
	speech_command(variant speaker, variant text)
	  : speaker_(speaker.try_convert<unit>()), text_(text.as_string())
	{}

	virtual void execute(scenario& s) const {
		if(speaker_) {
			set_scroll_target(speaker_->loc());
		}

		gui::dialog dialog(0, graphics::screen_height() - 300, graphics::screen_width(), 300);

		std::vector<std::string> lines = word_wrap_text(text_);
		int text_x = 10, text_y = 30;
		foreach(const std::string& line, lines) {
			gui::label* label = new gui::label(line, graphics::color_white(), 18);
			dialog.add_widget(gui::widget_ptr(label), text_x, text_y);
			text_y += label->height();
		}

		gui::widget_ptr description_widget;

		graphics::texture portrait;
		if(speaker_) {
			portrait = graphics::texture::get(speaker_->portrait());

			gui::label* label = new gui::label(speaker_->description(), graphics::color_white(), 28);
			gui::dialog* description_dialog = new gui::dialog(dialog.x() + portrait.width()/2 - label->width()/2, dialog.y() - label->height()/2 - 10, label->width() + 10, label->height() + 10);
			description_dialog->add_widget(gui::widget_ptr(label), 5, 5);
			description_widget = gui::widget_ptr(description_dialog);
		}

		frame_rate_handler fps;
		for(;;) {
			SDL_Event event;
			while(SDL_PollEvent(&event)) {
				switch(event.type) {
					case SDL_QUIT:
						throw human_player::game_aborted();
					case SDL_KEYDOWN:
						return;
				}
			}

			human_player::current_perspective()->draw(s);
			graphics::blit_texture(portrait, dialog.x(), dialog.y() - portrait.height());
			dialog.draw();

			if(description_widget) {
				description_widget->draw();
			}
			wm::swap_buffers();
			fps.next_frame();
			next_draw_cycle();
		}
	}
};

class speech_function : public function_expression
{
public:
	speech_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 2, 2)
	{}
private:
	variant execute(const formula_callable& variables) const {
		return variant(new speech_command(
		  args()[0]->evaluate(variables),
		  args()[1]->evaluate(variables)));
	}
};

class remove_handler_command : public game_command_callable {
public:
	virtual void execute(scenario& s) const {
		s.remove_current_handler();
	}
};

class remove_handler_function : public function_expression
{
public:
	remove_handler_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 0, 0)
	{}
private:
	variant execute(const formula_callable& variables) const {
		return variant(new remove_handler_command);
	}
};

class set_command : public game_command_callable
{
public:
	explicit set_command(variant target, const std::string& attr, variant val)
	  : target_(target), attr_(attr), val_(val)
	{}
	virtual void execute(scenario& s) const {
		if(target_.is_callable()) {
			target_.mutable_callable()->mutate_value(attr_, val_);
		} else {
			s.mutate_value(attr_, val_);
		}
	}
private:
	variant target_;
	std::string attr_;
	variant val_;
};

class set_function : public function_expression {
public:
	set_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 2, 3) {
	}
private:
	variant execute(const formula_callable& variables) const {
		variant target;
		if(args().size() == 3) {
			target = args()[0]->evaluate(variables);
		}
		const int begin_index = args().size() == 2 ? 0 : 1;
		return variant(new set_command(
		    target,
		    args()[begin_index]->evaluate(variables).as_string(),
			args()[begin_index + 1]->evaluate(variables)));
	}
};

class debug_command : public game_command_callable {
	variant v_;
public:
	explicit debug_command(variant v) : v_(v)
	{}

	virtual void execute(scenario& s) const {
		std::cerr << "FORMULA DEBUG: " << v_.to_debug_string() << "\n";
	}
};

class debug_function : public function_expression
{
public:
	debug_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 1, 1)
	{}
private:
	variant execute(const formula_callable& variables) const {
		std::cerr << "DEBUG CALLED\n";
		return variant(new debug_command(args()[0]->evaluate(variables)));
	}
};

class loc_function : public function_expression
{
public:
	loc_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 2, 2)
	{}
private:
	variant execute(const formula_callable& variables) const {
		return variant(new hex::location(args()[0]->evaluate(variables).as_int(), args()[1]->evaluate(variables).as_int()));
	}
};

class spawn_command : public game_command_callable {
public:
	spawn_command(int player, const std::string& type, const hex::location& loc, const std::string& soldier_type, int num_soldiers)
	  : player_(player), type_(type), loc_(loc), soldier_type_(soldier_type), num_soldiers_(num_soldiers)
	{}

	virtual void execute(scenario& s) const {
		ASSERT_LOG(player_ >= 0 && player_ < s.players().size(),
		           "INVALID PLAYER NUMBER: " << player_);
		player_ptr p = s.players()[player_];
		wml::node_ptr node(new wml::node("commander"));
		node->set_attr("type", type_);
		node->set_attr("x", formatter() << loc_.x());
		node->set_attr("y", formatter() << loc_.y());

		wml::node_ptr soldier_node(new wml::node("soldier"));
		soldier_node->set_attr("type", soldier_type_);
		soldier_node->set_attr("duplicates", formatter() << num_soldiers_);
		node->add_child(soldier_node);

		commander_unit_ptr cmd(new commander_unit(*p, node));
		s.add_commander(p, cmd);
	}
private:
	int player_;
	std::string type_;
	hex::location loc_;
	std::string soldier_type_;
	int num_soldiers_;
};

class spawn_function : public function_expression
{
public:
	spawn_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 5, 5)
	{}
private:
	variant execute(const formula_callable& variables) const {
		return variant(new spawn_command(
		               args()[0]->evaluate(variables).as_int(),
					   args()[1]->evaluate(variables).as_string(),
					   *args()[2]->evaluate(variables).try_convert<hex::location>(),
					   args()[3]->evaluate(variables).as_string(),
					   args()[4]->evaluate(variables).as_int()));
	}
};

class scenario_result_function : public function_expression
{
public:
	scenario_result_function(const std::string& fn, const args_list& args)
	  : function_expression(fn, args, 1, 2)
	{}
private:
	variant execute(const formula_callable& variables) const {
		scenario_result* result = new scenario_result;
		result->outcome = args()[0]->evaluate(variables).as_string();
		if(args().size() > 1) {
			result->next_scenario = args()[1]->evaluate(variables).as_string();
		}
		return variant(result);
	}
};

expression_ptr formula_game_logic_symbol_table::create_function(
                           const std::string& fn,
                           const std::vector<expression_ptr>& args) const
{
#define REGISTER(arg) if(fn == #arg) { \
	                       return expression_ptr(new arg##_function(fn, args)); \
	                  }
	REGISTER(debug);
	REGISTER(speech);
	REGISTER(remove_handler);
	REGISTER(set);
	REGISTER(loc);
	REGISTER(spawn);
	REGISTER(scenario_result);
#undef REGISTER
	return function_symbol_table::create_function(fn, args);
}

}

function_symbol_table& get_game_functions_symbol_table()
{
	static formula_game_logic_symbol_table table;
	return table;
}

}
